package com.mintyfresh.tns.client;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import com.mintyfresh.tns.util.*;

public class GameWindow extends JComponent {
	/* Representation of the terrain, used in creating the terrainPolygon */
	private Terrain terrain;

	/*
	 * Boolean to tell whether the game has started 
	 * Used to tell whether to draw splash screen or game screen
	 */
	private boolean gameStarted = false;

	/*
	 * These are the layers of the graphics object,
	 * in the order they are drawn onscreen 
	 */
	private Image backgroundImage;
	private Image bulletImage;
	private Image explosionImage;
	private Rectangle infoRectangle;
	private String powerString;
	private String angleString;
	private String timeRemainingString;
	private String windString;
	private String roundNumberString;
	private Polygon terrainPoly;
	private Map<Pair, Image> tanks;
	private Map<Pair, String> names;

	private java.util.List<Pair> bullets
		= Collections.synchronizedList(new LinkedList<Pair>());
	private boolean explosion;

	/* Used for constructing the terrain */
	private double percentStep = .005;
	private int startHeight = 400;
	private int xMax = 800;
	private int yMax = 600;

	/* Power and angle, set by a KeyboardListener */
	private int power = 0;
	private int angle = 0;

	/* Minty Fresh(tm) Physics Engine */
	private Physics engine;
	private LinkedList<Pair> path = new LinkedList<Pair>();

	/* Used by update to draw the bullets in a timed fashion */
	private long time = new Date().getTime();

	private int roundNr = 0;
	private boolean turn = false;

	/**
	 * Construct a new window with a wait message
	 * The real fun hits when startNewRound is called
	 */
	public GameWindow() {
		backgroundImage = ImageLoader.getImage("/images/splash.jpg");
		bulletImage = ImageLoader.getImage("/images/bullet.gif");
		explosionImage = ImageLoader.getImage("/images/explosion.gif");
		tanks = new HashMap<Pair, Image>();
		names = new HashMap<Pair, String>();
		this.addKeyListener(new KeyboardListener());
		this.setFocusable(true);
		this.requestFocusInWindow();
	}

	public void paint(Graphics g) {
		Graphics2D g2d = (Graphics2D)g;
		// Paint background image
		g2d.drawImage(backgroundImage, 0, 0, this);
		if(!gameStarted) return;
		// Paint info box
		g2d.setColor(Color.LIGHT_GRAY);
		g2d.fill(infoRectangle);
		g2d.setColor(Color.BLACK);
		g2d.drawString(powerString, 190, 20);
		g2d.drawString(angleString, 350, 20);
		g2d.drawString(windString, 510, 20);
		g2d.drawString(roundNumberString, 670, 20);
		// Paint terrain
		g2d.setColor(Color.LIGHT_GRAY);
		synchronized(terrainPoly) {
			g2d.fill(terrainPoly);
		}
		// Paint tanks
		synchronized(tanks) {
			for(Pair pair : tanks.keySet()) {
				int x = (int)pair.x;
				int y = (int)pair.y;
				g2d.drawImage(tanks.get(pair), x-40, y-25, this);
			}
		}
		//Paint names
		synchronized(names) {
			g2d.setColor(Color.WHITE);
			for(Pair pair : names.keySet()) {
				int x = (int)pair.x;
				int y = (int)pair.y;
				g2d.drawString(names.get(pair), x-40, y-35);
			}
		}

		if(bullets.size() <= 0) return;
		// Paint bullets;
		synchronized(bullets) {
			for(Pair pair : bullets) {
				g2d.drawImage(bulletImage, (int)pair.x-5, (int)pair.y-5, this);
			}
			// Paint explosion, if needed
			if(explosion) {
				Pair p = bullets.get(bullets.size()-1);
				g2d.drawImage(explosionImage, (int)p.x-35, (int)p.y-35, this);
			}
		}
	}

	private void removeTanks(Stack<Pair> tanksToRemove){
		synchronized(tanks) {
			synchronized(names) {
				while(!tanksToRemove.empty()) {
					tanks.remove(tanksToRemove.peek());
					names.remove(tanksToRemove.pop());
				}
			}
		}
	}

	private void destroyTerrain(int x){
		terrain.destroyTerrain(x);
		redrawPolygon();
	}

	private void redrawPolygon() {
		terrainPoly.reset();
		terrainPoly.addPoint(0, yMax);
		terrainPoly.addPoint(0, startHeight);

		for(Pair p : terrain.getPairs()) {
			terrainPoly.addPoint((int)p.x, (int)p.y);
		}
		terrainPoly.addPoint(xMax, yMax);
	}

	/**
	 * Called by Client
	 * Receive turn results and update the game window
	 * @author James
	 */
	public void shotResults(Pair origin, int power, double angle) {
		bullets = Collections.synchronizedList(new LinkedList<Pair>());
		engine.pairs(
				origin,
				power,
				angle,
				terrain); 
		LinkedList<Pair> path = engine.list();
		Pair end = path.getLast();
		while(path.size() > 0) {
			if( new Date().getTime() - time > 40 ) {
				Pair p = path.remove();
				bullets.add(p);
				this.repaint((int)p.x-5, (int)p.y-5, 10, 10);
				if(path.size() == 1) {
					path.remove();
					long startTime = new Date().getTime();
					explosion = true;
					while(new Date().getTime() - startTime < 1500) {
						//wait for explosion...
						this.repaint((int)end.x-50, (int)end.y-50, 100, 100);
					}
					explosion = false;
					destroyTerrain((int)end.x);
					this.repaint((int)end.x-50, (int)end.y-50, 100, 100);
				}
				time = new Date().getTime();
			}
		}
		Stack<Pair> tanksToRemove = new Stack<Pair>();
		int x = (int)end.x;
		for(Pair tank : tanks.keySet()) {
			int X = (int)tank.x;
			if(((x-40) <= X) && (X <= (x+40))) {
				tanksToRemove.push(tank);
			}
		}
		removeTanks(tanksToRemove);
		this.repaint();
		bullets = Collections.synchronizedList(new LinkedList<Pair>());
			}

	/**
	 * Called by Client
	 * Clears the GameWindow, updates with new information
	 * @author James
	 */
	public void newRound(
			Terrain terrain, Physics engine, HashMap <String, Pair> players) {
		//Set up background image
		int backgroundNumber = new Random().nextInt(6);
		backgroundImage = ImageLoader.getImage(
				"/images/background" + backgroundNumber + ".jpg");

		//Set up the terrain
		this.terrain = terrain;
		terrainPoly = new Polygon();
		terrainPoly.addPoint(0, yMax);
		terrainPoly.addPoint(0, startHeight);
		Collection<Pair> pairs = terrain.getPairs();
		for(Pair p : pairs) {
			terrainPoly.addPoint((int)p.x, (int)p.y);
		}
		terrainPoly.addPoint(xMax, yMax);

		//Set up the info box
		infoRectangle = new Rectangle(0, 0, 800, 30);
		powerString = "Power: " + power;
		angleString = "Angle: " + angle;
		windString = "Wind: " + engine.wind();
		roundNumberString = "Round: " + ++roundNr;

		//Create the Physics Engine
		this.engine = engine; 

		//Place the tanks
		String tankpath = "/images/tank";
		String extension = ".gif";
		int i = 0;
		tanks = Collections.synchronizedMap(new HashMap<Pair, Image>());
		names = Collections.synchronizedMap(new HashMap<Pair, String>());
		for(String name : players.keySet()) {
			Pair pair = players.get(name);
			Image tankImage = ImageLoader.getImage(tankpath + i + extension);
			tanks.put(pair, tankImage);
			names.put(pair, name);
			i++;
			if(i >= 3) i = 0;
		}

		//We can draw the game screen now
		gameStarted = true;
		this.repaint();
	}

	public void signalPlayerTurn() {
		turn = true;
	}

	public void endRound() {

	}

	public void incPower() {
		if(power < 100)
			power++;
		powerString = "Power: " + power;
		this.repaint(0, 0, 800, 30);
		Toolkit.getDefaultToolkit().beep();
	}

	public void decPower() {
		if(power > 0)
			power--;
		powerString = "Power: " + power;
		this.repaint(0, 0, 800, 30);
		Toolkit.getDefaultToolkit().beep();
	}

	public void incAngle() {
		if(angle < 180)
			angle++;
		angleString = "Angle: " + angle;
		this.repaint(0, 0, 800, 30);
		Toolkit.getDefaultToolkit().beep();
	}	

	public void decAngle() {
		if(angle > 0)
			angle--;
		angleString = "Angle: " + angle;
		this.repaint(0, 0, 800, 30);
		Toolkit.getDefaultToolkit().beep();
	}

	public void shoot() {
		if (turn) {
			Client.sendShot(power, angle);
			turn = false;
			Toolkit.getDefaultToolkit().beep();
		}
	}
}
